package com.obixlabs.commons.util;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Properties;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class PropertyOverrideLoader {

	private static final Logger logger = LoggerFactory.getLogger(PropertyOverrideLoader.class);
	
	private static final String CLASSPATH_SEPARATOR = "/";	

	private static final String DEV_ENV_NAME = "dev";	
	private static final String OBIX_ENV_PROPERTY = "obix.env";
	private static final String CURR_USR_PROPERTY = "user.name";
	
	private static final String ENV_PATH_PREFIX = "env";
	private static final String USR_PATH_PREFIX = "usr";
	
	private Properties properties;

	public PropertyOverrideLoader() {
		this.properties = new Properties();
		this.properties.putAll(System.getProperties());
	}
	
	public PropertyOverrideLoader(String propertiesPath){
		this();
		Properties appProperties = 
				PropertyOverrideLoader.getPropertiesWithOverrides(propertiesPath);
		this.properties.putAll(appProperties);
	}
	
	public PropertyOverrideLoader(String[] propertyFiles) {
		this();
		Properties appProperties = 
				PropertyOverrideLoader.getPropertiesWithOverrides(propertyFiles);
		this.properties.putAll(appProperties);		
	}
	
	public Properties getProperties() { return this.properties; }
	
	public static Properties getPropertiesWithOverrides(String[] propertyFiles) {
		Properties result = new Properties();		
		Properties propsFromFile;
		for (String file : propertyFiles) {
			propsFromFile = PropertyOverrideLoader.getPropertiesWithOverrides(file);
			result.putAll(propsFromFile);
		}		
		logger.info(	"\n\nConsolidated application properties {} :\n\n" + toString(result),
							Arrays.toString(propertyFiles));		
		return result;
	}

	public static Properties getPropertiesWithOverrides(String propertiesPath) {
		Properties result = new Properties();		
		Tuple<String, String> pathAndFileName = splitPathFromFileName(propertiesPath);
		loadPropertiesFromPath(result, propertiesPath);
		loadEnvironmentOverrides(result, pathAndFileName.getFirst(), pathAndFileName.getSecond());
		loadUserOverrides(result, pathAndFileName.getFirst(), pathAndFileName.getSecond());		
		logger.info("{} properties loaded from {}.", result.size(), propertiesPath);
		return result;
	}

	private static void loadUserOverrides(Properties result, 
																String path,
																String filename) {
		String currentUser = System.getProperty(CURR_USR_PROPERTY);
		logger.info("Loading  {} overrides, for user {}.", filename, currentUser);
		if (!StringUtils.isEmpty(currentUser)) {
			String propertiesFilePath 
				= getStartOfPath(path, USR_PATH_PREFIX) 
					+ CLASSPATH_SEPARATOR + currentUser  
					+ CLASSPATH_SEPARATOR + filename;
			loadPropertiesFromPath(result, propertiesFilePath);
		}
	}

	private static String getStartOfPath(String path, String subPath) {
		String result;
		if (StringUtils.isEmpty(path))
			result = subPath;
		else result = path + CLASSPATH_SEPARATOR  + subPath;
		return result;
	}

	private static void loadEnvironmentOverrides(	Properties result,
																				String path, 
																				String filename) {
		String environmentName = System.getProperty(	OBIX_ENV_PROPERTY, 
																					DEV_ENV_NAME); 
		logger.info("Loading environment overrides for {}.", environmentName);
		String propertiesFilePath = 
						getStartOfPath(path, ENV_PATH_PREFIX)
						+ CLASSPATH_SEPARATOR + environmentName 
						+ CLASSPATH_SEPARATOR + filename;
		loadPropertiesFromPath(result, propertiesFilePath);
	}

	private static void loadPropertiesFromPath(Properties result,
																		String propertiesPath) {
		try (InputStream classpathStream = 
				PropertyOverrideLoader.class.getResourceAsStream(propertiesPath)) {
			if (classpathStream!=null)
				result.load(classpathStream);
			else logger.warn("Null stream returned for classpath {}.", propertiesPath);
		} catch (IOException exce) {
			logger.warn("No properties found at path {}.", propertiesPath);
		}		
	}

	private static Tuple<String, String> splitPathFromFileName(
																	String propertiesPath) {
		Tuple<String, String> result;		
		String path;
		int fileNameStartIndex;
		int lastOccurenceOfPathSeparator = 
				propertiesPath.lastIndexOf(CLASSPATH_SEPARATOR);
		if (lastOccurenceOfPathSeparator > 0) {
			path = propertiesPath.substring(0, lastOccurenceOfPathSeparator);
			fileNameStartIndex = lastOccurenceOfPathSeparator + 1;
		} else if (lastOccurenceOfPathSeparator==0) {
			path = CLASSPATH_SEPARATOR;
			fileNameStartIndex = 1;
		} else {
			path = "";
			fileNameStartIndex=0;
		}		
		String fileName = propertiesPath.substring(fileNameStartIndex);
		result = new Tuple<String, String>(path, fileName);		
		return result;
	}
	
	private static String toString(Properties properties) {
		String result;
		
		StringWriter writer = new StringWriter();
		try (PrintWriter printWriter  = new PrintWriter(writer)) {
			list(properties, printWriter);
			result = writer.toString();
		} catch (Exception exce) {result = "";}
		return result;
	}

	private static void list(Properties properties, PrintWriter out) {
		out.println(" --  listing properties --");
		String key, value;
		for (Enumeration<Object> e = properties.keys(); e.hasMoreElements();) {
			key = (String) e.nextElement();
			value = (String)properties.getProperty(key);
			if (StringUtils.isEmpty(value))
				value = "<null>";
			else if (value.length() > 40) 
				value = value.substring(0,37) + "...";
			
			if (key.toLowerCase().contains("passw")) 
				value = "*********";
			
			out.println(key + "=" + value);
		}
	}	 
}